#include "GlfwWindow.h"
#include "GLFW/glfw3.h"
#include "OpenGLException.h"

bool  GlfwWindowManager::m_firstMouse = true;
float GlfwWindowManager::m_lastXPos   = 0.0f;
float GlfwWindowManager::m_lastYPos   = 0.0f;

float GlfwWindowManager::m_fov   = 45.0f;
float GlfwWindowManager::m_yaw   = -90.0f;
float GlfwWindowManager::m_pitch = 0.0f;

glm::vec3 GlfwWindowManager::m_cameraPos   = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 GlfwWindowManager::m_cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 GlfwWindowManager::m_cameraUp    = glm::vec3(0.0f, 1.0f, 0.0f);

GlfwWindowManager::GlfwWindowManager(int width, int height) : m_width(width), m_height(height), m_deltaTime(0.0f), m_lastFrame(0.0f)
{
    if (!glfwInit())
        throw OpenGLException("glfwInit failed!");

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
}

GlfwWindowManager::~GlfwWindowManager()
{
    glfwTerminate();
}

void GlfwWindowManager::framebufferSizeCallback(GLFWwindow *window, int width, int height)
{
    glViewport(0, 0, width, height);
}

void GlfwWindowManager::keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
{
    if (key == GLFW_KEY_ESCAPE)
        glfwSetWindowShouldClose(window, true);
}

void GlfwWindowManager::mouseCallback(GLFWwindow *window, double xpos, double ypos)
{
    if (m_firstMouse) {
        m_lastXPos   = xpos;
        m_lastYPos   = ypos;
        m_firstMouse = false;
    }

    float xoffset = xpos - m_lastXPos;
    float yoffset = m_lastYPos - ypos;
    m_lastXPos    = xpos;
    m_lastYPos    = ypos;

    float sensitivity = 0.05;
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    m_yaw += xoffset;
    m_pitch += yoffset;

    if (m_pitch > 89.0f)
        m_pitch = 89.0f;
    if (m_pitch < -89.0f)
        m_pitch = -89.0f;

    float x       = cos(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
    float y       = sin(glm::radians(m_pitch));
    float z       = sin(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
    m_cameraFront = glm::normalize(glm::vec3(x, y, z));
}

void GlfwWindowManager::scrollCallback(GLFWwindow *window, double xoffset, double yoffset)
{
    if (m_fov >= 1.0f && m_fov <= 45.0f)
        m_fov -= yoffset;
    if (m_fov <= 1.0f)
        m_fov = 1.0f;
    if (m_fov >= 45.0f)
        m_fov = 45.0f;
}

void GlfwWindowManager::processKeyboardInput()
{
    float cameraSpeed = 2.5f * m_deltaTime;
    if (glfwGetKey(m_window, GLFW_KEY_W) == GLFW_PRESS)
        m_cameraPos += cameraSpeed * m_cameraFront;

    if (glfwGetKey(m_window, GLFW_KEY_S) == GLFW_PRESS)
        m_cameraPos -= cameraSpeed * m_cameraFront;

    if (glfwGetKey(m_window, GLFW_KEY_A) == GLFW_PRESS)
        m_cameraPos -= glm::normalize(glm::cross(m_cameraFront, m_cameraUp)) * cameraSpeed;

    if (glfwGetKey(m_window, GLFW_KEY_D) == GLFW_PRESS)
        m_cameraPos += glm::normalize(glm::cross(m_cameraFront, m_cameraUp)) * cameraSpeed;

    if (glfwGetKey(m_window, GLFW_KEY_SPACE) == GLFW_PRESS)
        m_cameraPos += cameraSpeed * m_cameraUp;

    if (glfwGetKey(m_window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS)
        m_cameraPos -= cameraSpeed * m_cameraUp;
}

bool GlfwWindowManager::initWindow(const char *title)
{
    m_window = glfwCreateWindow(m_width, m_height, title, NULL, NULL);
    if (!m_window)
        return false;

    // 设置鼠标显示模式
    glfwSetInputMode(m_window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    glfwSetCursorPosCallback(m_window, mouseCallback);
    glfwSetScrollCallback(m_window, scrollCallback);
    glfwSetKeyCallback(m_window, keyCallback);
    glfwMakeContextCurrent(m_window);
    glfwSetFramebufferSizeCallback(m_window, framebufferSizeCallback);

    // glad 初始化
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        glfwDestroyWindow(m_window);
        return false;
    }
    return true;
}

void GlfwWindowManager::loopRender(std::function<void()> lambda)
{
    while (!glfwWindowShouldClose(m_window)) {
        float currentFrame = glfwGetTime();
        m_deltaTime        = currentFrame - m_lastFrame;
        m_lastFrame        = currentFrame;

        processKeyboardInput();

        lambda();

        glfwSwapBuffers(m_window);
        glfwPollEvents();
    }
}